package info.fastpace.utils;

import java.util.concurrent.CancellationException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public abstract class CancelableTask extends Observable<CancelableTask> implements Runnable {
	private volatile boolean isStarted = false;
	private volatile boolean isCanceled = false;
	private volatile boolean isSuccess = false;
	private volatile Exception e;
	private volatile AtomicBoolean doneLock = new AtomicBoolean(false);
	protected final AtomicInteger progress = new AtomicInteger(0);

	public CancelableTask() {
	}

	@Override
	public final void run() {
		try {
			runUnsafe();
		} catch (Exception e) {
			Config.getLog().e("", e);
		}
	}

	public final void runUnsafe() throws Exception {
		Config.getLog().d("Running cancelable task: " + toString());
		notifyObservers(this);
		isStarted = true;
		try {
			if (!isCanceled) {
				runImpl();
			}
		} catch (Exception e) {
			// Note: Cancel may throw exception
			if (doneLock.compareAndSet(false, true)) {
				this.e = e;
				notifyObservers(this);
				clearObservers();
				// Someone else should do something with the exception
				Config.getLog().w("Failed cancelable task: " + toString());
				throw e;
			}
			// Cancel got to the lock first but may NOT have yet changed the
			// cancel flag.
			// Must throw cancellation exception.
		}
		if (doneLock.compareAndSet(false, true)) {
			isSuccess = true;
			progress.set(100);
			notifyObservers(this);
			clearObservers();
			Config.getLog().d("Running cancelable task: " + toString());
			return;
		}

		// The task was canceled but the isCanceled may not have been set yet.

		synchronized (doneLock) { // Waiting for the cancel to finish it's logic
		}

		// assert isCanceled; // Commented out because android crashes the app
		// in assertion
		// No need to notify here because cancel was already notified in
		// cancel method.
		// notifyObservers(this);
		Config.getLog().d("Already canceled task: " + toString());
		throw new CancellationException("Canceled while running!");
	}

	protected abstract void runImpl() throws Exception;

	protected void cancelImpl() {
	}

	public final void cancel() {
		synchronized (doneLock) {
			if (doneLock.compareAndSet(false, true)) {
				Config.getLog().d("Canceling cancelable task: " + toString());
				isCanceled = true;

				cancelImpl();

				notifyObservers(this);

				clearObservers();
			}
		}
	}

	public final boolean isCanceled() {
		return isCanceled;
	}

	public final boolean isSuccessful() {
		return isSuccess;
	}

	public final boolean isDone() {
		return doneLock.get();
	}

	public final boolean isStarted() {
		return isStarted;
	}

	public final Exception getError() {
		return e;
	}

	public int getProgress() {
		return progress.get();
	}

	/**
	 * Observers will be cleared after the task is done but only after all of
	 * them are notified.
	 */
	@Override
	public void addObserver(Observer<CancelableTask> observer) {
		super.addObserver(observer);
	}
}
